/*
 *    Copyright 2009-2012 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.scripting.xmltags;

import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.parsing.TokenHandler;
import org.apache.ibatis.session.Configuration;

import java.util.Map;

/**
 * @author Clinton Begin
 */
/**
 * foreach SQL节点
 *TODO
 */
public class ForEachSqlNode implements SqlNode {
  public static final String ITEM_PREFIX = "__frch_";

  /**
   * 用于判断循环的终止条件， ForeachSqlNode 构造方法中会创建该对象
   */
  private ExpressionEvaluator evaluator;
  /**
   * 迭代的集合表达式
   */
  private String collectionExpression;
  /**
   * 记录了该 ForeachSqlNode 节点的子节点
   */
  private SqlNode contents;
  /**
   * 在循环开始前要添加的字符串
   */
  private String open;
  /**
   * 在循环结束后要添加的字符串
   */
  private String close;
  /**
   * 循环过程中，每项之间的分隔符
   */
  private String separator;

  /**
   * index 是当前迭代的次数， item 的值是本次选代的元素 若迭代集合是 Map ，则 index 是键， item 是值
   */
  private String item;
  private String index;
  /**
   * 配置对象
   */
  private Configuration configuration;

  public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index, String item, String open, String close, String separator) {
    this.evaluator = new ExpressionEvaluator();
    this.collectionExpression = collectionExpression;
    this.contents = contents;
    this.open = open;
    this.close = close;
    this.separator = separator;
    this.index = index;
    this.item = item;
    this.configuration = configuration;
  }

  @Override
  public boolean apply(DynamicContext context) {
    //获取参数信息
    Map<String, Object> bindings = context.getBindings();
	//解析collectionExpression->iterable,核心用的ognl
    //步骤1：解析集合表达式对应的实际参数
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    if (!iterable.iterator().hasNext()) {
      return true;
    }
    boolean first = true;
	//加上(
    //步骤2：在循环开始之前，调用 DynamicContext.appendSql()方法添 open 指定的字符串
    applyOpen(context);
    int i = 0;
    for (Object o : iterable) {
      //记录当前 DynamicContext 对象
      DynamicContext oldContext = context;
      //步骤 3：创建 PrefixedContext ，并让 context 指向该 PrefixedContext 对象
      if (first) {
        //如采是集合的第一项，则将 PrefixedContext.prefix 初始化为空字符串
        context = new PrefixedContext(context, "");
      } else if (separator != null) {
        //如采指定了分隔符，则PrefixedContext.prefix初始化为指定分隔符
        context = new PrefixedContext(context, separator);
      } else {
        //未指定分隔符，如 PrefixedContext.prefix 初始化为空字符串
          context = new PrefixedContext(context, "");
      }
      //uniqueNumber从0开始，每次递增 ，用于转换生成新的"#{}"占位符名称
      int uniqueNumber = context.getUniqueNumber();
      // Issue #709
      if (o instanceof Map.Entry) {
        @SuppressWarnings("unchecked")
        //如采集合是Map类型，将集合中key和value添加到 DynamicContext.bindings 集合中保存
        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
        //步骤4
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
        //步骤5
        applyItem(context, mapEntry.getValue(), uniqueNumber);
      } else {
        //将集合中的索引和元素添加到 DynamicContext.bindings 集合中保存
		//索引
        applyIndex(context, i, uniqueNumber);
		//加上一个元素
        applyItem(context, o, uniqueNumber);
      }
      //步骤6调用子节点的 apply()方法进行处理，注意 ，这里使用的 FilteredDynamicContext对象
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
      if (first) {
        first = !((PrefixedContext) context).isPrefixApplied();
      }
      //还原成原来 context
      context = oldContext;
      i++;
    }
	//加上)
    //步骤7 ：循环结束后，调用 DynamicContext.appendSql()方法添加 close 指定的字符串
    applyClose(context);
    return true;
  }

  /**
   * 注意 applyIndex()方法以及后面介绍的 appleyItem()方法的第三个参数 ，该值由
   * IDynamicContext 产生，且在每个 DynamicContext 对象的生命周期中是单调递增的
   * 读者可以结合 orEachSqlNode.apply()方法的代码进行理解
   * param context
   * @param o
   * @param i
   */
  private void applyIndex(DynamicContext context, Object o, int i) {
    if (index != null) {
      //key 为工ndex, value 是集合元素
      context.bind(index, o);
      //为index 添加前级和后缀形成新的 key
      context.bind(itemizeItem(index, i), o);
    }
  }

  private void applyItem(DynamicContext context, Object o, int i) {
    if (item != null) {
      context.bind(item, o);
      context.bind(itemizeItem(item, i), o);
    }
  }

  private void applyOpen(DynamicContext context) {
    if (open != null) {
      context.appendSql(open);
    }
  }

  private void applyClose(DynamicContext context) {
    if (close != null) {
      context.appendSql(close);
    }
  }

  private static String itemizeItem(String item, int i) {
    //／添加 "_frch" 前级和i后缀
    return new StringBuilder(ITEM_PREFIX).append(item).append("_").append(i).toString();
  }

  //被过滤的动态上下文
  private static class FilteredDynamicContext extends DynamicContext {
    /**
     * DynamicContext 对象
     */
    private DynamicContext delegate;
    /**
     * 对应集合项在集合中的索引位置
     */
    private int index;
    /**
     * 对应集合项的index，参见对 ForeachSqlNode.index 字段的介绍
     */
    private String itemIndex;
    /**
     * 对应集合项的 item
     */
    private String item;

    public FilteredDynamicContext(Configuration configuration,DynamicContext delegate, String itemIndex, String item, int i) {
      super(configuration, null);
      this.delegate = delegate;
      this.index = i;
      this.itemIndex = itemIndex;
      this.item = item;
    }

    @Override
    public Map<String, Object> getBindings() {
      return delegate.getBindings();
    }

    @Override
    public void bind(String name, Object value) {
      delegate.bind(name, value);
    }

    @Override
    public String getSql() {
      return delegate.getSql();
    }

    @Override
    public void appendSql(String sql) {
      //创建 GenericTokenParser 解析怒，注意这里匿名实现的 TokenHandler 对象
      GenericTokenParser parser = new GenericTokenParser("#{", "}", new TokenHandler() {
        @Override
        public String handleToken(String content) {
          // 对item 进行处理
          String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
          if (itemIndex != null && newContent.equals(content)) {
            //对 itemIndex 进行处理
            newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
          }
          return new StringBuilder("#{").append(newContent).append("}").toString();
        }
      });
      //／将解析后的 SQL 语句 片段追加到 delegate 中保存
      delegate.appendSql(parser.parse(sql));
    }

    @Override
    public int getUniqueNumber() {
      return delegate.getUniqueNumber();
    }

  }


  //前缀上下文
  private class PrefixedContext extends DynamicContext {
    /**
     * 底层封装的 DynamicContext 对象
     */
    private DynamicContext delegate;
    /**
     *指定的前缀
     */
    private String prefix;
    /**
     * 是否已经处理过前缀
     */
    private boolean prefixApplied;

    public PrefixedContext(DynamicContext delegate, String prefix) {
      super(configuration, null);
      this.delegate = delegate;
      this.prefix = prefix;
      this.prefixApplied = false;
    }

    public boolean isPrefixApplied() {
      return prefixApplied;
    }

    @Override
    public Map<String, Object> getBindings() {
      return delegate.getBindings();
    }

    @Override
    public void bind(String name, Object value) {
      delegate.bind(name, value);
    }

    @Override
    public void appendSql(String sql) {
      //判断是否需要追加前缀
      if (!prefixApplied && sql != null && sql.trim().length() > 0) {
        //追加前缀
        delegate.appendSql(prefix);
        //表示已经处理过前缀
        prefixApplied = true;
      }
      //追 sql 片段
      delegate.appendSql(sql);
    }

    @Override
    public String getSql() {
      return delegate.getSql();
    }

    @Override
    public int getUniqueNumber() {
      return delegate.getUniqueNumber();
    }
  }

}
